package com.gomoku

import android.graphics.Point
import android.os.Handler
import android.os.Handler.Callback
import android.os.Looper
import android.os.Message
import android.text.TextUtils

import androidx.compose.foundation.Canvas
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp

import java.io.ObjectInputStream
import java.io.ObjectOutputStream
import java.net.InetAddress
import java.net.InetSocketAddress
import java.net.Socket

import kotlin.math.min

// 游戏
class Game: Socket, Callback, Runnable {
    // 棋子类型
    enum class Piece {
        NOTHING,    // 什么也没有
        BLACK,      // 黑棋
        WHITE       // 白棋
    }

    // 状态
    enum class Status {
        END,            // 结束
        PLAYING,        // 自己下棋中
        RIVAL_PLAYING   // 对手下棋中
    }

    /**
     * 发送数据的线程。
     */
    private inner class SendThread(sendData: GomokuProtocol): Thread() {
        private val mSendData = sendData    // 要发送的数据

        /**
         * 线程方法。
         */
        override fun run() {
            try {
                mSender.writeObject(mSendData)
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
    }

    // 初始化成员
    private val mGameCallback: Handler =
        Handler(Looper.getMainLooper(), this)           // 游戏回调
    private var mIsBlack: Boolean                       // 黑棋则为 true，白棋则为 false
    private val mLastPos: Point                         // 上一次放置棋子的位置
    private val mPieceRadius: Float                     // 棋子半径
    private lateinit var mPieces:
        SnapshotStateList<SnapshotStateList<Piece>>     // 所有棋子
    private var mRivalName: String                      // 对手的名字
    private lateinit var mSender: ObjectOutputStream    // 数据发送者
    private var mStatus: Status                         // 状态
    private val mUICallback: Handler                    // UI 回调
    private var mUsername: String                       // 用户名

    /**
     * 构造方法。
     * @param callback  回调
     */
    constructor(callback: Callback) {
        mIsBlack = true
        mLastPos = Point()
        mPieceRadius = Const.GRID_SIZE * 4 / 10
        mRivalName = ""
        mStatus = Status.END
        mUICallback = Handler(Looper.getMainLooper(), callback)
        mUsername = ""

        // 开启接收数据的线程
        Thread(this).start()
    }

    /**
     * 接受或拒绝挑战。
     * @param rivalName 对手的名字
     * @param accept    接受挑战则为 true，否则为 false
     */
    fun acceptInvite(rivalName: String, accept: Boolean) {
        // 如果接受则游戏开始
        if (accept) {
            mRivalName = rivalName
            this.start(false)
        }

        // 发送结果
        val result = InviteResult(accept, mUsername, rivalName)
        this.sendData(result)
    }

    /**
     * 检查横向。
     * @return 胜利则返回 true，否则返回 false
     */
    private fun checkHorizontal(): Boolean {
        val checkPiece = if (mIsBlack) Piece.BLACK else Piece.WHITE // 要检查的棋子
        var count = 0   // 连续的棋子的数量

        // 从左到右检查一行
        for (i in 0 ..< Const.GAME_BOARD_POINT_COUNT_H) {
            // 查找棋子
            if (mPieces[mLastPos.y][i] == checkPiece) {
                count++
            } else {
                count = 0
            }

            // 如果5个棋子连在一起就胜利
            if (count == Const.WIN_NUMBER) {
                return true
            }
        }

        return false
    }

    /**
     * 检查斜向（从左上到右下）。
     * @return 胜利则返回 true，否则返回 false
     */
    private fun checkOblique1(): Boolean {
        val checkPiece = if (mIsBlack) Piece.BLACK else Piece.WHITE // 要检查的棋子
        var count = 0   // 连续的棋子的数量
        val min = min(mLastPos.x.toDouble(), mLastPos.y.toDouble()).toInt() // 范围坐标，用以确定左边界和上边界
        val left = mLastPos.x - min // 左边界
        val top = mLastPos.y - min  // 上边界

        // 从左上到右下检查一斜行
        var i = left
        var j = top
        while (i < Const.GAME_BOARD_POINT_COUNT_H && j < Const.GAME_BOARD_POINT_COUNT_V) {
            // 查找棋子
            if (mPieces[j][i] == checkPiece) {
                count++
            } else {
                count = 0
            }

            // 如果5个棋子连在一起就胜利
            if (count == Const.WIN_NUMBER) {
                return true
            }
            i++
            j++
        }

        return false
    }

    /**
     * 检查斜向（从左下到右上）。
     * @return 胜利则返回 true，否则返回 false
     */
    private fun checkOblique2(): Boolean {
        val checkPiece = if (mIsBlack) Piece.BLACK else Piece.WHITE // 要检查的棋子
        var count = 0   // 连续的棋子的数量
        var left = 0    // 左边界
        val bottom: Int // 下边界

        // 计算左边界和下边界
        if (mLastPos.x + mLastPos.y < Const.GAME_BOARD_POINT_COUNT_H) {
            bottom = mLastPos.x + mLastPos.y
        } else {
            left = (mLastPos.x + mLastPos.y) - Const.GAME_BOARD_POINT_COUNT_H + 1
            bottom = Const.GAME_BOARD_POINT_COUNT_V - 1
        }

        // 从左下到右上检查一斜行
        var i = left
        var j = bottom
        while (i < Const.GAME_BOARD_POINT_COUNT_H && j >= 0) {
            // 查找棋子
            if (mPieces[j][i] == checkPiece) {
                count++
            } else {
                count = 0
            }

            // 如果5个棋子连在一起就胜利
            if (count == Const.WIN_NUMBER) {
                return true
            }
            i++
            j--
        }

        return false
    }

    /**
     * 检查纵向。
     * @return 胜利则返回 true，否则返回 false
     */
    private fun checkVertical(): Boolean {
        val checkPiece = if (mIsBlack) Piece.BLACK else Piece.WHITE // 要检查的棋子
        var count = 0   // 连续的棋子的数量

        // 从上到下检查一列
        for (j in 0 ..< Const.GAME_BOARD_POINT_COUNT_V) {
            // 查找棋子
            if (mPieces[j][mLastPos.x] == checkPiece) {
                count++
            } else {
                count = 0
            }

            // 如果5个棋子连在一起就胜利
            if (count == Const.WIN_NUMBER) {
                return true
            }
        }

        return false
    }

    /**
     * 关闭游戏。
     */
    override fun close() {
        // 发送退出游戏请求
        val quitGame = QuitGame(mUsername)
        this.sendData(quitGame)

        try {
            // 延迟一下，防止数据还没发送完成就关闭网络
            Thread.sleep(100)

            // 关闭网络
            super.close()
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    /**
     * 认输。
     */
    fun defeat() {
        // 发送游戏结束数据
        val gameOver = GameOver(GameOverReason.SELF_ADMIT, mRivalName)
        this.sendData(gameOver)

        // 游戏结束
        this.gameOver(GameOverReason.SELF_ADMIT)
    }

    /**
     * 画游戏区域。
     * @param drawer        绘图者
     * @param gameBoardSize 游戏区域尺寸
     */
    private fun drawGameBoard(drawer: DrawScope, gameBoardSize: Float) {
        // 画线
        for (i in 1 .. Const.GAME_BOARD_POINT_COUNT_H) {
            // 画横线
            drawer.drawLine(
                color = Const.GAME_BOARD_BORDER_COLOR,
                strokeWidth = Const.GAME_BOARD_PEN_WIDTH,
                start = Offset(Const.GRID_SIZE, i * Const.GRID_SIZE),
                end = Offset(gameBoardSize - Const.GRID_SIZE, i * Const.GRID_SIZE)
            )

            // 画竖线
            drawer.drawLine(
                color = Const.GAME_BOARD_BORDER_COLOR,
                strokeWidth = Const.GAME_BOARD_PEN_WIDTH,
                start = Offset(i * Const.GRID_SIZE, Const.GRID_SIZE),
                end = Offset(i * Const.GRID_SIZE, gameBoardSize - Const.GRID_SIZE)
            )
        }
    }

    /**
     * 画所有棋子。
     * @param drawer    绘图者
     */
    private fun drawPieces(drawer: DrawScope) {
        for (i in 0 ..< Const.GAME_BOARD_POINT_COUNT_V) {
            for (j in 0 ..< Const.GAME_BOARD_POINT_COUNT_H) {
                if (mPieces[i][j] == Piece.NOTHING) {
                    continue
                }
                drawer.drawCircle(
                    color = if (mPieces[i][j] == Piece.BLACK) Color.Black else Color.White,
                    radius = mPieceRadius,
                    center = Offset((j + 1) * Const.GRID_SIZE, (i + 1) * Const.GRID_SIZE)
                )
            }
        }
    }

    /**
     * 寻找其它用户。
     */
    fun findUsers() {
        if (TextUtils.isEmpty(mUsername)) {
            return
        }

        val request = FindUsersRequest(mUsername)
        this.sendData(request)
    }

    /**
     * 创建游戏区域。
     * @param gameBoardSize 游戏区域尺寸，单位：像素
     */
    @OptIn(ExperimentalFoundationApi::class)
    @Composable
    fun GameBoard(gameBoardSize: Float) {
        // 初始化成员
        mPieces = remember {
            mutableStateListOf<SnapshotStateList<Piece>>()
        }
        for (i in 0 ..< Const.GAME_BOARD_POINT_COUNT_V) {
            val subList = remember {
                mutableStateListOf<Piece>()
            }
            repeat(Const.GAME_BOARD_POINT_COUNT_H) {
                subList.add(Piece.NOTHING)
            }
            mPieces.add(i, subList)
        }

        // 创建游戏区域
        val gameBoardSizeDp = (gameBoardSize / LocalDensity.current.density).dp
        Canvas(Modifier
            .width(gameBoardSizeDp)
            .height(gameBoardSizeDp)
            .background(Const.GAME_BOARD_COLOR)
            .pointerInput(this) {
                this.detectTapGestures(
                    onTap = {
                        onGameBoardClicked(gameBoardSize, it.x, it.y)
                    }
                )
            }
        ) {
            drawGameBoard(this, gameBoardSize)
            drawPieces(this)
        }
    }

    /**
     * 游戏结束。
     * @param reason    结束原因
     */
    private fun gameOver(reason: GameOverReason) {
        // 将状态设置为已结束
        mStatus = Status.END

        // 发送通知
        val message = Message.obtain()
        message.what = Const.UM_GAME_OVER
        message.arg1 = reason.ordinal
        mUICallback.sendMessage(message)
    }

    /**
     * 处理寻找其它用户结果。
     * @param result    结果
     */
    private fun handleFindUsers(result: FindUsersResult) {
        // 发送通知
        val message = Message.obtain()
        message.what = Const.UM_GET_FIND_USERS_RESULT
        message.obj = result
        mUICallback.sendMessage(message)
    }

    /**
     * 处理游戏结束。
     * @param gameOver  游戏结束信息
     */
    private fun handleGameOver(gameOver: GameOver) {
        // 如果没在下棋中，则直接返回
        if (mStatus == Status.END) {
            return
        }

        // 将状态设置为已结束
        mStatus = Status.END

        // 发送通知
        var reason = gameOver.mReason
        if (reason == GameOverReason.WON) {
            reason = GameOverReason.LOST
        } else if (reason == GameOverReason.SELF_ADMIT) {
            reason = GameOverReason.RIVAL_ADMIT
        }
        val message = Message.obtain()
        message.what = Const.UM_GAME_OVER
        message.arg1 = reason.ordinal
        mUICallback.sendMessage(message)
    }

    /**
     * 处理挑战请求。
     * @param request 请求
     */
    private fun handleInviteRequest(request: InviteRequest) {
        // 发送通知
        val message = Message.obtain()
        message.what = Const.UM_GET_INVITE_REQUEST
        message.obj = request.mRivalName
        mUICallback.sendMessage(message)
    }

    /**
     * 处理挑战结果。
     * @param result  结果
     */
    private fun handleInviteResult(result: InviteResult) {
        // 如果接受则游戏开始
        if (result.mAccept) {
            mRivalName = result.mRivalName
            this.start(true)
        }

        // 发送通知
        val message = Message.obtain()
        message.what = Const.UM_GET_INVITE_RESULT
        message.arg1 = if (result.mAccept) 1 else 0
        message.obj = result.mRivalName
        mUICallback.sendMessage(message)
    }

    /**
     * 处理登录结果。
     * @param result    结果
     */
    private fun handleLogon(result: LogonResult) {
        val succeeded = !TextUtils.isEmpty(result.mUsername)
        if (succeeded) {
            // 保存用户名
            mUsername = result.mUsername
        }

        // 发送通知
        val message = Message.obtain()
        message.what = Const.UM_GET_LOGON_RESULT
        message.obj = succeeded
        mUICallback.sendMessage(message)
    }

    /**
     * 自定义事件的响应方法。
     * @param message   事件
     * @return 事件在此被处理则返回 true，否则返回 false
     */
    override fun handleMessage(message: Message): Boolean {
        if (message.what == Const.UM_PUT_PIECE) {
            // 放一个与自己颜色相反的棋子
            this.putPiece(!mIsBlack, message.arg1, message.arg2)

            // 将状态设置为游戏中
            mStatus = Status.PLAYING
            return true
        }
        return false
    }

    /**
     * 处理放置棋子。
     * @param putPiece  棋子信息
     */
    private fun handlePutPiece(putPiece: PutPiece) {
        val message = Message.obtain()
        message.what = Const.UM_PUT_PIECE
        message.arg1 = putPiece.mX
        message.arg2 = putPiece.mY
        mGameCallback.sendMessage(message)
    }

    /**
     * 处理对手退出游戏事件。
     * @param quitGame  退出游戏信息
     */
    private fun handleQuitGame(quitGame: QuitGame) {
        // 如果不是当前对手，则直接返回
        if (quitGame.mUsername != mRivalName) {
            return
        }

        // 发送通知
        val message = Message.obtain()
        message.what = Const.UM_RIVAL_QUIT_GAME
        message.arg1 = if (mStatus != Status.END) 1 else 0
        message.obj = quitGame.mUsername
        mUICallback.sendMessage(message)

        // 对手名置空
        mRivalName = ""

        // 将状态设置为已结束
        mStatus = Status.END
    }

    /**
     * 发起挑战。
     * @param rivalName 要挑战的用户的名字
     */
    fun invite(rivalName: String) {
        if (TextUtils.isEmpty(rivalName)) {
            return
        }

        // 发送挑战请求
        val request = InviteRequest(mUsername, rivalName)
        this.sendData(request)
    }

    /**
     * 判断是否胜利。
     * @return 胜利则返回 true，否则返回 false
     */
    private val isWon: Boolean
        get() {
            // 检查横向
            var result = this.checkHorizontal()
            if (result) {
                return true
            }

            // 检查纵向
            result = this.checkVertical()
            if (result) {
                return true
            }

            // 检查斜向（从左上到右下）
            result = this.checkOblique1()
            if (result) {
                return true
            }

            // 检查斜向（从左下到右上）
            return this.checkOblique2()
        }

    /**
     * 登录。
     * @param username  用户名
     */
    fun logon(username: String) {
        // 发送登录请求
        val request = LogonRequest(username)
        this.sendData(request)
    }

    /**
     * 游戏区域的单击响应方法。
     * @param gameBoardSize 游戏区域尺寸
     * @param x, y          用户单击的位置
     */
    fun onGameBoardClicked(gameBoardSize: Float, x: Float, y: Float) {
        val bound = Const.GRID_SIZE / 2 // 格的尺寸的一半

        // 如果不是己方下棋中（对手下棋中或游戏结束）或者 单击的范围超出棋盘，则直接返回
        if (mStatus != Status.PLAYING || x < bound || x > gameBoardSize - bound || y < bound || y > gameBoardSize - bound) {
            return
        }

        // 将用户单击的点转换为离得最近的棋盘点
        var tempX = x
        var tempY = y
        var temp = x % Const.GRID_SIZE
        if (temp >= bound) {
            tempX += Const.GRID_SIZE - temp
        } else {
            tempX -= temp
        }

        temp = y % Const.GRID_SIZE
        if (temp >= bound) {
            tempY += Const.GRID_SIZE - temp
        } else {
            tempY -= temp
        }

        // 将窗口坐标转换成棋盘坐标
        tempX = tempX / Const.GRID_SIZE - 1
        tempY = tempY / Const.GRID_SIZE - 1

        // 放置棋子
        val put = this.putPiece(mIsBlack, tempX.toInt(), tempY.toInt())
        if (!put) {
            return
        }

        // 判断是否胜利
        val won = this.isWon

        // 将已放置的棋子发给对手
        val putPiece = PutPiece(mRivalName, tempX.toInt(), tempY.toInt())
        this.sendData(putPiece)

        // 如果胜利则通知对手，否则等待对手下棋
        if (won) {
            // 发送游戏结束数据
            val gameOver = GameOver(GameOverReason.WON, mRivalName)
            this.sendData(gameOver)

            // 游戏结束
            this.gameOver(GameOverReason.WON)
        } else {
            mStatus = Status.RIVAL_PLAYING
        }
    }

    /**
     * 处理服务器发来的数据。
     * @param pro   协议
     */
    private fun onGetMessageFromServer(pro: GomokuProtocol) {
        when (pro.mType) {
            GomokuProtocol.Type.FIND_USERS -> this.handleFindUsers(pro as FindUsersResult)
            GomokuProtocol.Type.GAME_OVER -> this.handleGameOver(pro as GameOver)
            GomokuProtocol.Type.INVITE_REQUEST -> this.handleInviteRequest(pro as InviteRequest)
            GomokuProtocol.Type.INVITE_RESULT -> this.handleInviteResult(pro as InviteResult)
            GomokuProtocol.Type.LOGON -> this.handleLogon(pro as LogonResult)
            GomokuProtocol.Type.PUT_PIECE -> this.handlePutPiece(pro as PutPiece)
            GomokuProtocol.Type.QUIT_GAME -> this.handleQuitGame(pro as QuitGame)
        }
    }

    /**
     * 放置棋子。
     * @param isBlack   黑棋则为 true，白棋则为 false
     * @param x, y      棋盘坐标
     * @return 成功则返回 true，否则返回 false
     */
    fun putPiece(isBlack: Boolean, x: Int, y: Int): Boolean {
        // 如果超出棋盘区域，则返回 false
        if (x >= Const.GAME_BOARD_POINT_COUNT_H || y >= Const.GAME_BOARD_POINT_COUNT_V) {
            return false
        }

        // 如果当前点已经有棋子了，则返回 false
        if (mPieces[y][x] != Piece.NOTHING) {
            return false
        }

        // 保存最后放置棋子的位置
        mLastPos.x = x
        mLastPos.y = y

        // 保存到所有棋子中
        if (isBlack) {
            mPieces[y][x] = Piece.BLACK
        } else {
            mPieces[y][x] = Piece.WHITE
        }

        return true
    }

    /**
     * 游戏重新开始。
     */
    fun restart() {
        // 发送挑战请求
        val request = InviteRequest(mUsername, mRivalName)
        this.sendData(request)
    }

    /**
     * 接收数据的线程方法。
     */
    override fun run() {
        var receiver: ObjectInputStream? = null // 数据接收者
        val message = Message.obtain()
        message.what = Const.UM_CONNECTED

        try {
            // 连接服务器
            val serverAddress = InetAddress.getByName(GomokuProtocol.SERVER_IP)
            super.connect(InetSocketAddress(serverAddress, GomokuProtocol.SERVER_PORT))
        } catch (_: Exception) {
            message.obj = false
            mUICallback.sendMessage(message)
            return
        }
        message.obj = true
        mUICallback.sendMessage(message)

        try {
            // 创建数据发送者
            mSender = ObjectOutputStream(super.getOutputStream())

            // 创建数据接收者
            receiver = ObjectInputStream(super.getInputStream())

            // 循环接收数据，直到自己被关闭
            while (!super.isClosed()) {
                // 接收数据并转换成协议
                val pro: GomokuProtocol = receiver.readObject() as GomokuProtocol

                // 处理协议
                this.onGetMessageFromServer(pro)
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
        try {
            receiver?.close()
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    /**
     * 发送数据。
     * @param pro   协议
     */
    fun sendData(pro: GomokuProtocol) {
        SendThread(pro).start()

        // 防止发送过快导致服务器无法响应
        try {
            Thread.sleep(100)
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    /**
     * 游戏开始。
     * @param isBlack   黑棋则为 true，白棋则为 false
     */
    fun start(isBlack: Boolean) {
        // 清空所有棋子
        for (i in 0..<Const.GAME_BOARD_POINT_COUNT_H) {
            for (j in 0..<Const.GAME_BOARD_POINT_COUNT_V) {
                mPieces[i][j] = Piece.NOTHING
            }
        }

        // 设置成员变量
        mStatus = if (isBlack) Status.PLAYING else Status.RIVAL_PLAYING
        mIsBlack = isBlack

        // 发送通知
        val message = Message.obtain()
        message.what = Const.UM_GAME_START
        message.obj = mRivalName
        mUICallback.sendMessage(message)
    }
}
